'''
Created on 30/06/2012

@author: mekoda
'''

from main.errors import OutOfBoundsError, ProcessNotFoundInMatrixError, \
    NotARowError
from main.process import Process, PCB, IOInstruction, Instruction, ProcessStatus
from main.utils import getRemainingInstructionsOf, getConsumedInstructionsOf, \
    myListCopy

#------------------------------------------------------------#
#------------------------------------------------------------#
#------------------------------------------------------------#

class Matrix():
    def __init__(self, aColumnQuantity):
        self.colQt = aColumnQuantity
        self.rows = []
            
    def rowQt(self):
        return len(self.rows)
    
    def print(self):
        for row in self.rows:
            print(row.content)
            
    def addRow(self,aRow):
        if not isinstance(aRow, Row):
            raise NotARowError("Not Row object!")
        self.rows.append(aRow)
            
            
def hasRowOfProcess(aMatrix,aProcess):
    for row in aMatrix.rows:
        if aProcess.pcb.processID == row.content[0]:
            return True
    return False
    
def getRowOfProcess(aMatrix,aProcess):
    # USAGE:
    #process = Process(PCB(3))
    #process.pcb.processID = 1234
    #matrix = Matrix(4)
    #matrix.rows.append(Row())
    #row = matrix.rows[0]
    #row.content.append(1234)
    #print(getRowOfProcess(matrix,process))
    for row in aMatrix.rows:
        if(row.content[0] == aProcess.pcb.processID):
            return row
    raise ProcessNotFoundInMatrixError("The process is not depicted in the matrix")

def replaceRowOfProcess(aMatrix,aProcess,theContent):
    replaced = False
    for row in aMatrix.rows:
        if(row.content[0] == aProcess.pcb.processID):
            row.content = theContent 
            replaced = True
    if not replaced: 
        raise ProcessNotFoundInMatrixError("The process is not depicted in the matrix")
    
    
        

#------------------------------------------------------------#

            
class Row():
    def __init__(self):
        self.content = []
        
    def lessOrEqualThan(self,anotherRow):
    #USAGE:
    #fRow = Row()
    #fRow.content = [4,3]
    #sRow = Row()
    #sRow.content = [3,3]
    #print(fRow.lessOrEqualThan(sRow))
        if(len(self.content)!=len(anotherRow.content)):
            raise NotARowError("Todo mal")
    
        result = True
        
        for element in self.content:
            if not element <= anotherRow.content[self.content.index(element)]:
                result = False
        return result
    
    def add(self,anotherRow):
        index = 0
        newContents = []
        for elem in self.content:
            newContents.append(self.content[index]+anotherRow.content[index])
            index += 1
        
        self.content = newContents
        
    def print(self):
        print(self.content)

def addProcessResources(availableRow,theProcessRow):
    withoutID = theProcessRow
    if len(withoutID.content) != 0:
        withoutID.content.pop(0)
        return availableRow.add(withoutID)
    else:
        availableRow
                        

#------------------------------------------------------------#
#------------------------------------------------------------#
#------------------------------------------------------------#
            
    

class Banker():
    def __init__(self,aKernel, aListOfDevices,myMemory):
        self.memory = myMemory
        self.kernel = aKernel
        self.devicesInSystem = self.detectDevices(aListOfDevices)
        self.assigned = Matrix(len(self.devicesInSystem)+1) # +1 for the name
        self.required = Matrix(len(self.devicesInSystem)+1) # +1 for the name
        self.safeStateVerifier = SafeStateVerifier(aKernel)
        self.deviceQtInSystem = self.handleDevices(aListOfDevices)
        
        
    #####MAIN METHOD########################################
    #######################################################
    def isInASafeStateAdding(self,aProcess):
        self.safeStateVerifier.sandBox.append(aProcess)
        self.safeStateVerifier.prepareForVerification()
        self.initializeAssignedAndRequired()
        self.available = self.deviceQtInSystem
        #BANKER ITERATIONS
        for iteration in range(len(self.safeStateVerifier.sandBox)):
            for actualRow in range(len(self.assigned.rows)):
                #ASSIGNED ESTA VACIOOO
                if not (self.safeStateVerifier.hassEnded(self.required.rows[actualRow].content[0])):
                    crucial = self.requiredAreLessOrEqualThanAvailableOf(aProcess)
                    if crucial:
                        self.safeStateVerifier.notifyThatAProcessEnded(self.required.rows[actualRow].content[0])
                        addProcessResources(self.available,self.assigned.rows[actualRow])
        return self.safeStateVerifier.isInSafeState()
                    

    #####MAIN METHOD########################################
    #####################################################a##

    def initializeAssignedAndRequired(self):
        self.assigned.rows = []
        self.required.rows = []
        
        for process in self.safeStateVerifier.sandBox:
            self.initializeAssignedRowOfProcess(process)
            self.initializeRequiredRowOfProcess(process)
    
    def initializeAssignedRowOfProcess(self,aProcess):
        replaced = False
        for row in self.assigned.rows:
            if not row.content == []:
                if row.content[0] == aProcess.pcb.processID:
                    replaceRowOfProcess(self.assigned, aProcess, getAssignedRequirementsOfAProcess(self, aProcess))
                    replaced = True
                    
        if not replaced:
            self.assigned.addRow(getAssignedRequirementsOfAProcess(self, aProcess)) 
                                
    
    def initializeRequiredRowOfProcess(self,aProcess):
        if hasRowOfProcess(self.required, aProcess):
            replaceRowOfProcess(self.required, aProcess, getCurrentRequirementsOfAProcess(self, aProcess)) 
        else:
            self.required.addRow(getCurrentRequirementsOfAProcess(self, aProcess))           
        
    def handleDevices(self,aListOfDevices):
        ret = {}
        
        for device in aListOfDevices:
            if device.name in ret:
                ret[device.name] += 1
            else:
                ret[device.name] = 1
        ret['CPU'] = 1
        ret['MEMORY'] = self.memory.size
        
        final = Row()
        
        #HACK
        finalContent = []
        for elem in ret.values():
            finalContent.append(elem)
        #
        
        final.content = myListCopy(finalContent)
        final = final; 
        
        return final
    
    def detectDevices(self,aListOfDevices):
        ret = {}
        for device in aListOfDevices:
            if device.name not in ret:
                ret[device.name] = 0
        ret['CPU'] = 0
        ret['MEMORY'] = 0
    
        return ret
    
    def requiredAreLessOrEqualThanAvailableOf(self,aProcess):
        reqRow = getRowOfProcess(self.required,aProcess)
        reqRow.content.pop(0)
        avRow =  self.available
        
        return reqRow.lessOrEqualThan(avRow)

#def getTotalRequirementsOfAProcess(aBanker,aProcess):
#    ret = deepcopy(aBanker.devicesInSystem)
#    
#    #FIRST IO
#    for instruction in aProcess.instructions:
#        if isinstance(instruction,IOInstruction):
#            ret[instruction.name] += 1
#    
#    #THEN CPU
#    ret['CPU'] = 1
#    
#    #FINALLY MEMORY
#    #INTERESANTE: Considera que este todo en memoria
#    ret['MEMORY'] = aBanker.memory.neededMemoryCellsOf(aProcess)
#    
#    return ret.values()

def getCurrentRequirementsOfAProcess(aBanker,aProcess):
    ret = aBanker.devicesInSystem
    
    remainingInstructions = getRemainingInstructionsOf(aProcess)
    #FIRST IO
    for instruction in remainingInstructions:
        if isinstance(instruction,IOInstruction):
            if instruction.neededDevice in ret:
                ret[instruction.neededDevice] += 1
            else:
                ret[instruction.neededDevice] = 1     
                
    #THEN CPU
    ret['CPU'] = 1
    
    #FINALLY MEMORY
    #INTERESANTE: Considera que este todo en memoria
    ret['MEMORY'] = aBanker.memory.neededMemoryCellsOf(aProcess)
    
    final = list(ret.values())
    final.insert(0,aProcess.pcb.processID)
    final2 = Row()
    final2.content = myListCopy(final)
    return final2

def getAssignedRequirementsOfAProcess(aBanker,aProcess):
    if aProcess.instructions[0] == aProcess.pcb.currentInstruction:
        return Row()
    else: 
        ret = aBanker.devicesInSystem
    
        remainingInstructions = getConsumedInstructionsOf(aProcess)
        
        #FIRST IO
        for instruction in remainingInstructions:
            if isinstance(instruction,IOInstruction):
                if instruction.neededDevice in ret:
                    ret[instruction.neededDevice] += 1
                else:
                    ret[instruction.neededDevice] = 1     
        
        #THEN CPU
        ret['CPU'] = 1
        
        #FINALLY MEMORY
        #INTERESANTE: Considera que este todo en memoria
        ret['MEMORY'] = aBanker.memory.neededMemoryCellsOf(aProcess)
        
        final = list(ret.values())
        final.insert(0,aProcess.pcb.processID)
        final2 = Row()
        final2.content = final
        return final2
            
    

#------------------------------------------------------------#

class SafeStateVerifier():
    #USAGE:
    #p1 = Process(PCB(3))
    #p2 = Process(PCB(5))
    #l = [p1,p2]
    #s = SafeStateVerifier(l)
    #s.prepareForVerification()
    #print(s.isInSafeState())
    #s.notifyThatAProcessEnded(p1)
    #print(s.isInSafeState())
    #s.notifyThatAProcessEnded(p2)
    #print(s.isInSafeState())
    def __init__(self,aKernel):
        self.kernel = aKernel
        self.processes = []
        self.expected = 0
        self.finished = 0
        self.status = {}
        self.isCurrentlyWorking = False
        self.sandBox = []
        
    #--------------------------------#    
    
    def prepareForVerification(self):
        self.isCurrentlyWorking = True
        self.expected = len(self.sandBox)
        #Primero limpio el Dict para status viejos y cosas raras
        self.status.clear()
        
        self.finished = 0
        
        for process in self.sandBox:
                self.status[process.pcb.processID] = False
        
    
    def notifyThatAProcessEnded(self,aProcessID):  
        self.status[aProcessID] = True
        self.finished += 1
        
    def hassEnded(self,aProcessID):
        return self.status[aProcessID] == True
    
    def isInSafeState(self):
        return self.finished == self.expected
    
    def setOff(self):
        self.isCurrentlyWorking = False
        
    #--------------------------------#    

    def addProcess(self,aProcess):
        self.processes.append(aProcess)


